package jamirr;

import jamirr.loader.ServiceLoaderImpl;

import java.util.*;

final class BeanReferenceLoader {

    public static final BeanReferenceLoader INSTANCE = new BeanReferenceLoader();

    private Map<String, BeanReference<Object>> beanMirrorRefMap;
    private final ClassLoader classLoader;

    public BeanReferenceLoader() {
        this.classLoader = BeanReferenceLoader.class.getClassLoader();
    }

    public BeanReferenceLoader(ClassLoader classLoader) {
        this.classLoader = classLoader;
    }

    public <T> BeanMirror<T> getBeanMirror(Class<T> beanType) {
        return findIntrospection(beanType).orElseThrow(() -> new MirrorException("No bean introspection available for type [" + beanType + "]. Ensure the class is annotated with jamirr.Mirror"));
    }

    @SuppressWarnings({"java:S1181", "unchecked"})
    public <T> Optional<BeanMirror<T>> findIntrospection(Class<T> beanType) {
        Objects.requireNonNull(beanType, "beanType cannot be null");
        final BeanReference<T> reference =
                (BeanReference<T>) getBeanMirrorReferenceMap().get(beanType.getName());
        try {
            if (reference != null) {
                return Optional.of(reference).map(BeanReference::load);
            } else {
                return Optional.empty();
            }
        } catch (Throwable e) {
            throw new MirrorException("Error loading BeanMirror for type [" + beanType + "]: " + e.getMessage(), e);
        }
    }

    @SuppressWarnings("unchecked")
    private Map<String, BeanReference<Object>> getBeanMirrorReferenceMap() {
        Map<String, BeanReference<Object>> resolvedIntrospectionMap = this.beanMirrorRefMap;
        if (resolvedIntrospectionMap == null) {
            synchronized (this) { // double check
                resolvedIntrospectionMap = this.beanMirrorRefMap;
                if (resolvedIntrospectionMap == null) {
                    resolvedIntrospectionMap = new HashMap<>(30);
                    final ServiceLoaderImpl<BeanReference<Object>> services = loadReferences();
                    List<BeanReference<Object>> beanIntrospectionReferences = new ArrayList<>(300);
                    services.collectAll(
                            beanIntrospectionReferences,
                            BeanReference::isPresent
                    );
                    for (BeanReference<Object> reference : beanIntrospectionReferences) {
                        resolvedIntrospectionMap.put(reference.getName(), reference);
                    }
                    this.beanMirrorRefMap = resolvedIntrospectionMap;
                }
            }
        }
        return resolvedIntrospectionMap;
    }

    @SuppressWarnings({"java:S3740", "rawtypes"})
    private ServiceLoaderImpl loadReferences() {
        return ServiceLoaderImpl.load(BeanReference.class, classLoader);
    }
}
